package com.wyy.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.wyy.beans.*;
import com.wyy.dao.ParkLotDao;
import com.wyy.service.CarService;
import com.wyy.service.DriverService;
import com.wyy.service.ParkLotService;
import com.wyy.service.TicketService;
import com.wyy.common.Result;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.*;
import java.util.stream.Collectors;

import static java.lang.Double.parseDouble;


@Service
public class ParkLotServiceImpl implements ParkLotService {

    @Autowired
    private ParkLotDao parkLotDao;

    @Autowired
    private CarService carService;

    @Autowired
    private DriverService driverService;

    @Autowired
    private TicketService ticketService;

    private static final double R = 6371393.0; //单位m

    @Override
    public Result<ParkLot> addParkLot(ParkLot parkLot) {
        QueryWrapper<ParkLot> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq(true, "park_lot_no", parkLot.getParkLotNo());
        ParkLot newParkLot = parkLotDao.selectOne(queryWrapper);
        if (newParkLot != null) {
            return new Result<>(Result.ResultStatus.Failed.code, "停车场已存在");
        }
        int aff = parkLotDao.insert(parkLot);
        return aff == 0 ? new Result<>(Result.ResultStatus.Failed.code, "fail")
                : new Result<>(Result.ResultStatus.SUCCESS.code, "success");
    }

    @Override
    public Result<ParkLot> updateParkLotSpace(ParkLot parkLot) {
        QueryWrapper<ParkLot> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq(true, "park_lot_no", parkLot.getParkLotNo());
        ParkLot newParkLot = parkLotDao.selectOne(queryWrapper);
        if (newParkLot == null) {
            return new Result<>(Result.ResultStatus.Failed.code, "没有这个停车场");
        }

        parkLot.setId(newParkLot.getId());
        int aff = parkLotDao.updateById(parkLot);
        return aff == 0 ? new Result<>(Result.ResultStatus.Failed.code, "fail")
                : new Result<>(Result.ResultStatus.SUCCESS.code, "success");
    }

    /**
     * 数据库事务
     * 隔离级别 和 传播行为
     * 1： 使用 （transaction注解中的各个参数作用）
     * 2： 如果不用注解 怎么实现事务
     * 3. spring 是怎么实现事务的 （ ** ）
     */
    @Override
    public Result<Ticket> park(Car car, Driver driver) {

        Result<Car> newCar = carService.getCar(car.getLicensePlateNo());
        if (newCar.getData() != null) {
            return new Result<>(Result.ResultStatus.Failed.code, "不允许重复停车");
        }
        //距离不是固定的 是因为人和车不是固定的
        ParkLot parkLot = getConditionsMetParkLot(driver, 1);

        car.setTelephoneNumber(driver.getTelephoneNumber());
        Result<Car> carResult = carService.addCar(car);

        driverService.addDriver(driver);

        Ticket ticket = Ticket.builder()
                .parkLotNo(parkLot.getParkLotNo())
                .licensePlateNo(car.getLicensePlateNo())
                .telephoneNumber(driver.getTelephoneNumber())
                .parkTime(new Date())
                .build();

        Result<Ticket> ticketResult = ticketService.addTicket(ticket);

        parkLot.setAvailableSpaces(parkLot.getAvailableSpaces() - 1);
        Result<ParkLot> parkLotResult = updateParkLotSpace(parkLot);

        return hasFiled(carResult, ticketResult, parkLotResult)
                ? new Result<>(Result.ResultStatus.Failed.code, "fail")
                : new Result<>(Result.ResultStatus.SUCCESS.code, "success",ticket);
    }

    @Override
    public Result<List<Ticket>> huddleParking(List<Car> cars, List<Driver> drivers) {
        HashMap<Car, Driver> map = new HashMap<>();
        int flag = 0;
        for (Car car : cars) {
            Result<Car> newCar = carService.getCar(car.getLicensePlateNo());
            if (newCar.getData() != null) {
                flag ++;
                continue;
            }
            map.put(car,drivers.get(flag ++));
        }
        if (map.size() == 0) {
            return new Result<>(Result.ResultStatus.Failed.code, "不允许重复停车");
        }

        ParkLot parkLot = getConditionsMetParkLot(map.values().stream().findFirst().get(), map.size());

        ArrayList<Ticket> tickets = new ArrayList<>();

        map.keySet().forEach(car -> {
            Driver driver = map.get(car);
            car.setTelephoneNumber(driver.getTelephoneNumber());
            carService.addCar(car);

            driverService.addDriver(driver);

            Ticket ticket = Ticket.builder()
                    .parkLotNo(parkLot.getParkLotNo())
                    .licensePlateNo(car.getLicensePlateNo())
                    .telephoneNumber(driver.getTelephoneNumber())
                    .parkTime(new Date())
                    .build();

            ticketService.addTicket(ticket);
            tickets.add(ticket);
        });

        parkLot.setAvailableSpaces(parkLot.getAvailableSpaces() - map.size());
        updateParkLotSpace(parkLot);
        return new Result<>(Result.ResultStatus.SUCCESS.code, "success",tickets);
    }

    public ParkLot getConditionsMetParkLot(Driver driver, int parkCarSize) {
        Map<Double, ParkLot> getAllParkLotDistances = getAroundParkLotDistance(driver).getData();
        List<Double> distances = getAllParkLotDistances.keySet()
                .stream()
                .filter(distance -> distance < driver.getDistanceRange())
                .collect(Collectors.toList());

        return distances.stream()
                .map(getAllParkLotDistances::get)
                .filter(parkLot_t -> parkLot_t.getAvailableSpaces() > parkCarSize - 1)
                .min(Comparator.comparing(ParkLot::getParkingCosts))
                .get();
    }

    @Override
    public Result<Car> parkUp(Ticket ticket, Driver driver) {
        // 1.禁止重复取车
        Result<Ticket> parkMsg = getParkMsg(ticket.getLicensePlateNo());
        if (parkMsg.getStatus() == 500) {
            return new Result<>(Result.ResultStatus.Failed.code, parkMsg.getMessage());
        }

        Ticket newTicket = parkMsg.getData();

        // 2.根据票信息查找数据库进行比对
        if (!hasIssued(newTicket, ticket, driver)){
            return new Result<>(Result.ResultStatus.Failed.code, "fail");
        }

        ParkLot parkLot = getParkLot(newTicket.getParkLotNo()).getData();

        // 3.获取停车费用
        String parkingCosts = parkMsg.getMessage();

        // 4.parkUp
        Car car = carService.getCar(newTicket.getLicensePlateNo()).getData();
        Result<Car> deleteCar = carService.deleteCar(car.getLicensePlateNo());

        Result<Ticket> deleteTicket = ticketService.deleteTicket(newTicket.getLicensePlateNo());

        parkLot.setAvailableSpaces(parkLot.getAvailableSpaces() + 1);
        Result<ParkLot> parkLotResult = updateParkLotSpace(parkLot);

        return hasFiled(deleteCar, deleteTicket, parkLotResult)
                ? new Result<>(Result.ResultStatus.Failed.code, "fail")
                : new Result<>(Result.ResultStatus.SUCCESS.code, parkingCosts,car);
    }

    public boolean hasFiled(Result<Car> carResult, Result<Ticket> ticketResult, Result<ParkLot> parkLotResult) {
        return carResult.getStatus() == 500
                && ticketResult.getStatus() == 500
                && parkLotResult.getStatus() == 500;
    }

    @Override
    public Result<ParkLot> getParkLot(String parkLotNo) {
        QueryWrapper<ParkLot> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq(true, "park_lot_no", parkLotNo);
        ParkLot newParkLot = parkLotDao.selectOne(queryWrapper);
        return newParkLot == null ? new Result<>(Result.ResultStatus.Failed.code, "fail")
                : new Result<>(Result.ResultStatus.SUCCESS.code, "success", newParkLot);
    }

    @Override
    public Result<Ticket> getParkMsg(String licensePlateNo) {
        // 1.判断是否有该车
        Result<Ticket> ticketResult = ticketService.getTicket(licensePlateNo);
        if (ticketResult.getStatus() == 500) {
            return new Result<>(Result.ResultStatus.Failed.code, "查无此车");
        }

        Ticket ticket = ticketResult.getData();
        ParkLot parkLot = getParkLot(ticket.getParkLotNo()).getData();

        int parkingTime = ticketService.getParkingCosts(ticket);
        String parkingCosts = String.valueOf(parkingTime * parkLot.getParkingCosts());

        return new Result<>(Result.ResultStatus.SUCCESS.code
                , "停车时间(小时)：" + parkingTime + "，费用为：" + parkingCosts
                , ticket);
    }

    @Override
    public Result<Map<Double, ParkLot>> getAroundParkLotDistance(Driver driver) {
        List<ParkLot> parkLots = getParkLots().getData();
        TreeMap<Double, ParkLot> parkLotTreeMap = new TreeMap<>();
        for (ParkLot parkLot : parkLots) {
            double distance = getDistance(driver, parkLot);
            parkLotTreeMap.put(distance, parkLot);
        }
        return new Result<>(Result.ResultStatus.SUCCESS.code, "success", parkLotTreeMap);
    }



    @Override
    public double getDistance(Driver driver, ParkLot parkLot) {
        double parkLotLatitude = parseDouble(parkLot.getLatitude()); // 停车场纬度
        double parkLotLongitude = parseDouble(parkLot.getLongitude()); // 停车场经度
        double driverLatitude = parseDouble(driver.getLatitude()); // 司机纬度
        double driverLongitude = parseDouble(driver.getLongitude()); // 司机经度

        double parkLotRadian = radian(parkLotLatitude); // 弧度
        double driverRadian = radian(driverLatitude);
        double differenceLatitude = radian(parkLotLatitude) - radian(driverLatitude);//两点纬度之差
        double differenceLongitude = radian(parkLotLongitude) - radian(driverLongitude); //经度之差

        double distance = 2 * Math.asin(
                Math.sqrt(Math.pow(Math.sin(differenceLatitude / 2), 2)
                        + Math.cos(parkLotRadian) * Math.cos(driverRadian)
                        * Math.pow(Math.sin(differenceLongitude / 2), 2)));//计算两点距离的公式

        distance = distance * R;//弧长 * 地球半径
        distance = Math.round(distance * 10000) / 10000;//精确距离的数值
        return distance;
    }

    @Override
    public Result<List<ParkLot>> getParkLots() {
        List<ParkLot> parkLots = parkLotDao.findAllParkLots();
        return parkLots.size() == 0 ? new Result<>(Result.ResultStatus.Failed.code, "fail")
                : new Result<>(Result.ResultStatus.SUCCESS.code, "success", parkLots);
    }

    public double radian (double latitudeOrLongitude) {
        return latitudeOrLongitude * Math.PI / 180.0;
    }

    public boolean hasIssued(Ticket newTicket, Ticket ticket, Driver driver) {
        return newTicket.getParkLotNo().equals(ticket.getParkLotNo())
                && newTicket.getTelephoneNumber().equals(ticket.getTelephoneNumber())
                && driver.getTelephoneNumber().equals(newTicket.getTelephoneNumber());
    }


}
